Skip to content

Add Okiff and GREENNET distributors and the CUBA IoT Platform case study - #643

Open
rusikv wants to merge 10 commits into
thingsboard:mainfrom
rusikv:new-distributors-case-study
Open

Add Okiff and GREENNET distributors and the CUBA IoT Platform case study#643
rusikv wants to merge 10 commits into
thingsboard:mainfrom
rusikv:new-distributors-case-study

Conversation

@rusikv

@rusikv rusikv commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

Two additions to the partner/marketing pages:

  • Distributors (/partners/distributors/): adds Okiff (North America / Canada) and GREENNET (Europe + Asia / Ukraine, Poland, Georgia). GREENNET declares both regions because Georgia is transcontinental in REGION_MEMBERSHIP — same convention as Basari Muhendislik — so the Asia → Georgia filter path finds it.
  • Case study (/case-studies/cuba-iot-platform/): new "Backup power IoT in Kazakhstan: how CUBA IoT Platform monitors diesel generators with ThingsBoard" page, built 1:1 from the Figma case-studies design — data file, 7 local images (hero background, logo with alpha, 5 power-block screenshots exported from the design), and the standard CDN widget mockups for the help section.

Supporting changes a reviewer should know about:

  • The catalog card is deliberately placed to open page 2 of the case-studies grid (index 9 of caseStudyOrder, 9 cards per page) rather than newest-first — intentional placement, commented in index.ts.
  • New optional data flag CaseStudyData.logoUnfiltered: CUBA's logo is a shaded white-on-transparent mark, so the catalog card and contact CTA skip their brightness(0) invert(1) flatten for it and render the faceted logo the design shows. Format-agnostic — any future case study with a pre-styled logo (SVG or raster) sets this one field. All 39 existing studies keep the white treatment (verified the filter still applies to them).
  • Some copy is intentionally verbatim from the design even where phrasing is debatable (e.g. the "ThingsBoard & ThingsBoard Edge Implementation" block title, quote attribution "CTO in Skif Trade LLP") — flagged to the content owner separately; not silently edited.

Type of change

  • New / updated documentation page (src/content/docs/**)
  • Shared include file (src/content/_includes/**)
  • Component, layout, or styling change (src/components/**, src/styles/**)
  • Landing / use-case / case-study page (src/pages/**, src/data/**)
  • Redirects (src/data/redirects.ts)
  • Version bump / release (see release skill)
  • Build, CI, scripts, or tooling
  • Other (please describe)

Affected products

None — marketing pages only (/partners/distributors/, /case-studies/).

Checklist

  • pnpm check passes (Astro / TypeScript) — not run locally (it clears the content store and 500s any live dev server); relying on the CI run. ESLint on all changed files is clean.
  • pnpm lint:eslint passes
  • pnpm lint:slugcheck passes (required if pages were added/renamed/moved across languages)
  • pnpm lint:linkcheck passes locally — required to merge; run it before requesting review (use pnpm lint:linkcheck:nobuild if you already ran a build)
  • Renamed/removed pages have a redirect in src/data/redirects.ts, and pnpm generate:redirects was run — N/A, nothing renamed or removed
  • No hardcoded versions — values come from src/data/versions.ts
  • Screenshots attached for visual changes — verified in the browser against the design (hero, stats, quote, problem/power/help sections, CTA logo treatment, catalog pagination, distributor finder filters); pnpm lint:landmarks also passes on the build

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are additive, follow existing data/component patterns, and the new logoUnfiltered flag is consistently typed and applied where the filter is used.

Pull request overview

Adds two new distributor entries and introduces a new case study page (“CUBA IoT Platform”) to the ThingsBoard marketing site, including a small styling/data-model extension to support case-study logos that should not be white-flattened.

Changes:

  • Added Okiff and GREENNET to the distributor finder dataset.
  • Added the new cuba-iot-platform case study data entry (and ordered it intentionally within the catalog list).
  • Introduced an optional logoUnfiltered flag and wired it through the case-studies catalog card and the case-study contact CTA to disable the default logo filter when needed.
File summaries
File Description
src/pages/case-studies/index.astro Adds conditional classing + CSS to disable logo filtering on catalog cards when logoUnfiltered is set.
src/pages/case-studies/[slug].astro Passes logoUnfiltered through to the contact CTA for case study detail pages.
src/data/partners/distributors.ts Adds Okiff and GREENNET distributor entries (regions/countries/email/website).
src/data/case-studies/types.ts Adds CaseStudyData.logoUnfiltered and CaseStudyCard.logoUnfiltered typings and documentation.
src/data/case-studies/index.ts Adds cuba-iot-platform to catalog ordering and maps logoUnfiltered into derived card data.
src/data/case-studies/cuba-iot-platform.ts New case study content/data definition for the CUBA IoT Platform page.
src/components/CaseStudy/ContactCTA.astro Adds optional prop + conditional classing + CSS to skip logo filter for the company logo in the CTA.
Review details
  • Files reviewed: 7/14 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed 14 changed files in Add Okiff and GREENNET distributors and the CUBA IoT Platform case study. Left 12 comment(s) inline.

No correctness or safety bugs. The distributor data checks out: Canada, Ukraine, Poland and Georgia are all already classified in REGION_MEMBERSHIP and already named by existing distributors, so assertDistributorData passes and neither OFFERED_COUNTRIES nor the map SVG needs a change. Both new domains resolve and carry MX records, and each email matches its website domain. The logoUnfiltered plumbing is complete — grepping every brightness(0) invert(1) in src/ turns up exactly the two company-logo surfaces the PR patches (the third hit is the ThingsBoard logo in ContactCTA.astro:129, correctly untouched), and the catalog's client script only toggles is-hidden on server-rendered nodes rather than rebuilding card markup, so the modifier class survives search and pagination. The page-2 placement claim holds: PAGE_SIZE = 9 and the new slug sits at index 9.

Most of what's left is copy. Five of the notes below are straight proofreading against the conventions the other 40 case studies already follow, and they're the ones I'd actually fix before merge — particularly the missing full stop on the quote (the only one of 41 without terminal punctuation), "CTO in" (no sibling uses "in"), and "e-mail" (the only two occurrences in all of src/; the other 179 are "email").

Additional findings

These observations are about existing code outside the PR's diff — spotted while reading surrounding context.

  • src/pages/case-studies/index.astro:8 and :172PAGE_SIZE = 9 is declared twice, once in the frontmatter and again inside the client <script>, with no link between them. This is what makes the new caseStudyOrder comment fragile (see the inline note on index.ts:32).
  • src/pages/case-studies/[slug].astro:2-14 — this file mixes @data/case-studies with seven ../../ relative imports. CLAUDE.md asks for the @-prefixed aliases (@layouts/*, @components/*, …) and never relative paths. Pre-existing and not this PR's job, but the file is already being touched.

This review was auto-generated. Findings may contain errors — please verify before applying changes.

},

statistics: [
{ value: 100, suffix: '+', label: 'Monitored generator nodes in production experience' },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"in production experience" isn't idiomatic English — I can't quite tell what it's claiming. If it means the number is drawn from deployments already running, "Monitored generator nodes in production" says that cleanly; if it's about CUBA's track record, "Generator nodes monitored in production deployments" works.

Separate nit, take it or leave it: all three labels here are capitalized, while the great majority across the other case studies are lowercase noun phrases ('monitoring points installed', 'sensors installed', 'years of experience'). There are a couple of capitalized outliers already, so this isn't a rule — just noting it if you want the stat row to match its neighbours.

],

quote: {
text: 'With ThingsBoard and CUBA RePort gateways, diesel generators are no longer rarely inspected backup assets. Operators can see low fuel, discharged batteries, abnormal temperatures, fault states, missed test runs, and maintenance needs before they become outages',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quote is missing its full stop after "before they become outages". I checked all 41 quote texts in src/data/case-studies/ — this is the only one without terminal punctuation.

On line 34, "CTO in Skif Trade LLP" reads as a non-native construction; English takes "at" for a company. None of the 21 sibling role values use "in" — the established shapes are 'Founder, ACTE Technology', 'PM, SOLANDTEC', 'Founder & CEO of TJK-Solutions'. 'CTO at Skif Trade LLP' would fit. I know you flagged the attribution as verbatim-from-design, but this one is a grammar slip rather than a debatable phrasing choice, so it seems worth correcting even in verbatim copy. ("SMALL, SPAR supermarkets" is also opaque to a reader who doesn't know those chains — a word like "retail chains" would help, if the content owner is fine with deviating.)

results: [
'Continuous monitoring of fuel level, battery voltage, oil pressure, coolant temperature, generator/mains electrical parameters, runtime, start count, operating mode, and alarms',
'Centralized fleet table with sorting by site, generator type, operating status, fuel level, alarm count, coolant temperature, and control mode',
'Rule-based alarm activation and instant notifications through Telegram, SMS, and e-mail',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"e-mail" here and on line 71 are the only two occurrences of the hyphenated spelling anywhere in src/ — the other 179 are "email". Worth matching.

{
title: 'Unified asset model and dashboards',
text: 'ThingsBoard is configured around generator assets, sites, telemetry streams, alarms, and operator dashboards. Dispatchers can start from a fleet-level table, sort assets by status or alarm count, and drill down to a detailed generator page. The detailed panel consolidates engine state, generator electrical parameters, mains parameters, breaker states, runtime, and control mode.',
image: '/images/case-studies/cuba-iot-platform-2.webp',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think images 2 and 3 are paired with the wrong blocks. This block's text is about the asset model, the fleet table and the per-generator detail panel that "consolidates engine state, generator electrical parameters, mains parameters, breaker states, runtime, and control mode" — that is precisely what cuba-iot-platform-3.webp shows (left rail with Engine/Generator readings, Generator and Mains tables, breaker indicators). cuba-iot-platform-2.webp is four historical trend charts with an "Engine stop" annotation, which belongs with block 4. Your own alt strings say as much: image 2's is "Generator telemetry charts", image 3's is "…engine, generator, and mains panels".

Caveat: you built this 1:1 from the design, so it's quite possible the pairing is faithful and the mismatch originated there. In that case this is a question for the designer rather than something to fix in the PR — but the mismatch is real either way, so it's worth resolving before it ships.

Two related observations while you're here. Images 2 and 5 are both crops of the same historical dashboard that image 4 shows in full, so blocks 2, 4 and 5 currently render three near-identical chart grids — and a straight 2↔3 swap would leave the Rule Engine block holding image 2, which still shows no alarm or notification. A rule-chain or Telegram/e-mail notification screenshot would carry more weight there. Separately, image 3 has a Russian-language UI ("Карта" / "Таблица") on an otherwise English marketing page — worth an English capture if one is obtainable.

},
{
title: 'Historical analytics and service workflows',
text: 'Time-series widgets show current and historical data for any selected period. Operators can analyze mains outage, generator start, fuel refill, engine stop, voltage, current, power, oil pressure, coolant temperature, and battery behavior. This supports readiness proof, outage investigation, fuel theft detection, and maintenance planning by actual runtime.',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small copy things in this block. "maintenance planning by actual runtime" wants "based on actual runtime". And "Operators can analyze mains outage, generator start, fuel refill, engine stop, voltage, current, power…" mixes bare singular event nouns with mass nouns — pluralizing the events ("mains outages, generator starts, fuel refills, engine stops") makes the list read consistently.

Comment thread src/data/case-studies/index.ts Outdated
'onedata',
'lumen',
'solandtec',
// Intentionally out of newest-first order: opens catalog page 2 (9 cards per page).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment hard-codes the page size, but PAGE_SIZE = 9 actually lives in src/pages/case-studies/index.astro — and is declared twice there, once in the frontmatter (line 8) and once inside the client <script> (line 172). So "first card of page 2" rests on three mentions of the number 9 across two files, none of which references the others.

Exporting a single CASE_STUDY_PAGE_SIZE from this module and importing it in both spots would collapse that; I checked, and the client script is a bundled module (it already does import { updatePagination } from '@components/Pagination/pagination-client'), so the import works there. Low priority though — the bigger half is the why: the comment says what the placement does but not what it's for, so the next person tidying the array back to newest-first has no way to tell whether that's safe.

Comment thread src/data/case-studies/index.ts Outdated
logo: d.hero.logo,
logoAlt: d.hero.logoAlt,
logoHeight: cardLogoHeights[d.pageSlug],
logoUnfiltered: d.logoUnfiltered,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Adding a case study?" checklist above still lists two steps plus the optional cardLogoHeights entry — logoUnfiltered is now the other optional per-study logo knob and isn't mentioned. Without a bullet there, the next person shipping a logo that already has its own dark treatment will rediscover the white flatten by eye rather than by reading the list.

There's also a mild tension with the cardLogoHeights comment ("we keep the card override here rather than on the data itself") now that a card-affecting override does live on the data. One sentence drawing the line — card-only sizing here, cross-surface treatment on the data — would keep that rationale coherent.

Comment thread src/data/case-studies/types.ts Outdated
* white-on-transparent mark). Surfaces that normally white-flatten logos
* (catalog card, contact CTA) render it as-is instead.
*/
logoUnfiltered?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, and I wouldn't hold the PR for it: the name describes the CSS mechanism ("we don't run a filter") rather than the property of the asset ("this logo already ships light-on-dark artwork"), which is what a data-file author actually knows. Something like logoIsLightOnDark would let the data file be written without knowing how the card is styled. The doc comment covers the gap well enough that this is really just a preference — a boolean is the right shape either way.

},
{
name: "GREENNET",
regions: ["Europe","Asia"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declaring both regions does make Georgia reachable from the Asia filter as intended. One side effect worth being aware of: REGION_OFFERED_COUNTRIES in src/data/partners/index.ts collects all countries a distributor names for each region it declares, without intersecting against REGION_MEMBERSHIP. So picking region "Asia" now offers Ukraine and Poland in the country dropdown, and "Asia + Poland" returns GREENNET.

Basari Muhendislik already behaves the same way (Tunisia and Libya appear under Asia), so this is pre-existing behavior that your entry widens rather than introduces — and fixing it would change results for the existing entries too. Not something to do in this PR; worth a separate ticket if the dropdown noise bothers anyone, since REGION_MEMBERSHIP already has everything needed to intersect.

Comment thread src/pages/case-studies/index.astro Outdated
<div class="cs-card-top">
<img
class="cs-card-logo"
class:list={['cs-card-logo', { 'cs-logo-unfiltered': card.logoUnfiltered }]}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the same concept gets two names across the two surfaces — cs-logo-unfiltered here and cs-contact-logo-unfiltered in ContactCTA.astro. Nothing collides, since each is scoped under its own base class, but settling on one (cs-logo-unfiltered reads better as the generic modifier) makes the pair greppable as a single feature.

@rusikv
rusikv requested a review from vvlladd28 September 4, 2026 15:02

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review summary

Re-reviewed Add Okiff and GREENNET distributors and the CUBA IoT Platform case study — verified 12 finding(s) from previous review.

Status Count
✅ Resolved 11
💬 Acknowledged 0
❌ Unresolved 1

Also found 5 new issue(s) in the fix commits, commented inline.

The region work went well beyond what the last round asked for, and I verified the parts that could have gone wrong rather than assuming. REGION_OFFERED_COUNTRIES now intersects against REGION_MEMBERSHIP, which strips a lot of bogus cross-region options (66 from North America, 47 from Europe, 43 from South America, 16 from Asia) — and nothing broke behind it: still 168 countries offered overall, no offered (region, country) pair returns zero distributors, no distributor becomes unreachable, every named country still appears under at least one region, and assertDistributorData passes on the current dataset. The Intl.Collator change does what its comment claims ("Åland Islands" moves from index 167 to 0). The logoUnfilteredlogoIsLightOnDark rename is complete — grepping src/ for the old identifiers and cs-contact-logo-unfiltered turns up nothing. The page-2 claim holds (caseStudyOrder index 9, PAGE_SIZE 9, 38 slugs). The populateCountries(regionSelect.value) init call is correct on the browser-restore path: it snapshots countrySelect.value before rewriting the options and re-applies it, so a restored region+country pair survives a reload. And the new image 2 is the right screenshot for its block, in English — both halves of that finding.

Only one thing here is worth blocking on: HADEX keeps a region it now covers nothing in, and this PR is what made that true. Everything else inline is either a one-clause comment fix or a question for the content/design owner.

Deliberately not flagged, in case anyone else looks at the same lines and wonders: the || '9' fallback in the catalog client script is an unreachable branch (#cs-card-grid has exactly one render site and always sets data-page-size), and it matches what PartnerLibrary.astro:42 already does, so it's the right call rather than a compromise. The cs-logo-unfiltered modifier appearing in two components with two scoped rules is fine — each override sits three lines under the filter: brightness(0) invert(1) it cancels, which beats a global utility that would have to fight Astro's scoped-style specificity. .sort(byName) on partners/index.ts:31 is a no-op today (all seven REGION_MEMBERSHIP arrays are already collator-ordered) but is cheap insurance if a country ever lands out of order, so it should stay; getCoverage still using the default comparator is likewise inert, since that list is only joined into data-countries for matching and never rendered. And regionsOf's nested filter/includes runs over ~172 country/region pairs once per module load, so an inverse index would add more machinery than it saves.

On tests: this repo has no harness at all — no test script, no vitest/jest/playwright dependency, no *.test.ts outside node_modules. Data invariants live in module-load assertions and the scripts/lint-*.ts family, so I'm not asking for unit tests on the new branches; a lint:distributors script is the shape that would fit if this validation keeps growing.

Finding details

  • src/data/case-studies/cuba-iot-platform.ts:27 — stat labels: "in production experience" not idiomatic, and capitalized against convention — Fixed in code (relabelled and lowercased).
  • src/data/case-studies/cuba-iot-platform.ts:33 — quote missing its terminal full stop; "CTO in Skif Trade LLP" — Fixed in code (both applied).
  • src/data/case-studies/cuba-iot-platform.ts:46 — "e-mail" the only hyphenated spelling in src/Fixed in code; the two remaining hits in src/ are unrelated legal pages.
  • src/data/case-studies/cuba-iot-platform.ts:66 — images 2 and 3 paired with the wrong blocks — Fixed in code, and better than a straight swap: image 2 is a fresh English-UI export of the panel view, so the Russian-UI half is gone too. The two related asides in that comment are re-raised inline below.
  • src/data/case-studies/cuba-iot-platform.ts:77 — "maintenance planning by actual runtime"; singular event nouns in a mixed list — Fixed in code.
  • src/data/case-studies/cuba-iot-platform.ts:82 — Title Case block title and an unsupported ThingsBoard Edge claim — Fixed in code: retitled "ThingsBoard implementation", Edge claim dropped.
  • src/data/case-studies/cuba-iot-platform.ts:91industryName in Title Case, rendering mid-sentence — Fixed in code.
  • src/data/case-studies/index.ts:33 — the placement comment says what it does but not what it's forPartly addressed: the client script now reads data-page-size, so the fragility half is gone. The rationale still isn't there. Commented inline.
  • src/data/case-studies/index.ts:22 — "Adding a case study?" checklist didn't mention the new flag; tension with the cardLogoHeights rationale — Fixed in code: step 4 added and the rationale extended.
  • src/data/case-studies/types.ts:58logoUnfiltered names the CSS mechanism, not the asset property — Fixed in code: renamed to logoIsLightOnDark throughout.
  • src/data/partners/distributors.ts:722REGION_OFFERED_COUNTRIES didn't intersect against REGION_MEMBERSHIP, so Asia offered Ukraine and Poland — Fixed in code, and beyond what I suggested: the intersection plus a new build-time assertion that every named country's regions are declared. Verified no filter combination or distributor was lost.
  • src/pages/case-studies/index.astro:98cs-logo-unfiltered vs cs-contact-logo-unfiltered for one concept — Fixed in code (unified).

Both "Additional findings" from last round are also addressed: the duplicated PAGE_SIZE in the client script is gone, and [slug].astro's seven relative imports are now @-prefixed aliases.


This re-review was auto-generated. Findings may contain errors — please verify before applying changes.

{
name: "HADEX",
regions: ["Asia"],
regions: ["Asia","Middle East"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding Middle East is what makes Syria reachable under the new rule, but keeping Asia alongside it leaves a region HADEX covers nothing in — the table classifies Syria under Middle East only. So this line creates one of the 8 entries the new findDanglingRegions warning reports, in the same PR that adds the warning.

Was regions: ["Middle East"] the intent? I checked what that changes and it's inert everywhere except the thing you'd want: the assertion still passes, the warning for HADEX goes away, and the country dropdowns come out byte-identical (Syria was never offered under Asia anyway, since the new intersection excludes it). The only visible effect is that HADEX stops appearing under the Asia filter's "All countries" view — which is the correct outcome if Syria isn't Asian per our own table.

The Vehi.kz LLP change below is clean by contrast — both Asia and Europe have named countries in them.

if (errors.length > 0) {
throw new Error(`[distributors] ${errors.join(' | ')}`);
}
const dangling = findDanglingRegions(distributors, membership);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran this against the dataset as it stands on this branch and it fires immediately, for 8 entries:

[distributors] regions declared without any named country in them (card vanishes once a country is picked):
  Clever Platform → Asia; HADEX → Asia; LTS-Connecting Things → Asia; qnectd → Europe;
  Quantova → Asia; Spectrum Smart Solutions Systems LLC → Asia; Kar-Tel LLP → Middle East;
  IoT Experts → Asia

A check that's noisy on the day it lands stops carrying signal within a build or two, and then it won't catch the case it was written for. Six of the eight are the same shape — a Middle-East-only country (Saudi Arabia, Syria, UAE, Israel) with a leftover Asia claim.

I simulated dropping every dangling region to see what it would cost, and mechanically it costs nothing:

8 entries change (e.g. Clever Platform ["Middle East","Asia"] → ["Middle East"],
                       Kar-Tel LLP ["Asia","Europe","Middle East"] → ["Asia","Europe"])
assertion:                 passes, zero warnings
empty (region, country):   none
unreachable distributors:  none
country dropdowns:         identical — no option gained or lost

So the only real question is editorial: should those 8 cards keep showing under a region whose countries they don't serve, in the "All countries" view? That's a data-owner call, which is exactly why a warning rather than an error is the right shape here — I'd just rather the channel started silent. Clearing the HADEX one (see my note in distributors.ts) is yours since this PR created it; the other 7 are pre-existing and fine as a follow-up ticket.

If some of those claims turn out to be deliberate, there's a good in-repo precedent for saying so: src/data/case-studies/index.ts:97 keeps its equivalent warning quiet with an explicit FEATURED_ONLY_SLUGS allowlist.

* - Transcontinental countries appear under every region they span, so Turkey
* counts as Europe and Middle East, and Georgia as Europe and Asia.
* - Caribbean and Central American territories count as North America.
* - A distributor must declare every region its named countries fall under:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new bullet sits a little awkwardly next to the first convention in the same docblock — "Middle East is its own region, not a subset of Asia — a distributor covering both lists both". That sentence is very likely why 6 existing entries list Asia for Middle-East-only countries: read as "Middle East countries are Asian too, so list Asia", it produces exactly the entries the new dangling-region warning flags. Under the new bullet, those same entries are wrong.

So the table and those 6 entries disagree about whether Saudi Arabia / Syria / UAE / Israel are Asian, and this PR adds the check that surfaces the disagreement without settling it. Worth settling it here in one clause — either those countries stay Middle-East-only and the entries drop Asia (which is what the new rule implies), or they get dual-classified in the table the way Turkey and Georgia already are. Right now a reader following bullet 1 and a reader following bullet 4 will write the entry differently.

'onedata',
'lumen',
'solandtec',
// Intentionally out of newest-first order: first card of catalog page 2.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still unresolved: the fragility half is fixed — the client script reads data-page-size now, so the page size no longer lives in three unlinked places. But the part I'd flagged as the more important one is still open: the comment says what the placement does, not what it's for. The next person tidying this array back to newest-first has no way to tell whether that's safe, which is the failure mode the comment exists to prevent.

One clause would close it — why page 2 specifically, rather than the top of page 1 where a new case study would normally go? That reasoning is the only thing stopping someone from "fixing" the ordering later.

(No need to put the 9 back or export the constant — data-page-size was the right call, and it matches what PartnerLibrary.astro already does.)

{
title: 'Rule Engine alarms and operational notifications',
text: 'Rule chains process controller alarms, fuel level, battery voltage, coolant temperature, oil pressure, operating mode, and generator/mains state. Notifications are routed to responsible users through Telegram, SMS, and email. Each event can include the site, generator, source gateway, parameter, value, and timestamp, reducing the need for manual dashboard checks.',
image: '/images/case-studies/cuba-iot-platform-3.webp',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-raising the part of my earlier image note that the swap didn't cover. Blocks 2 and 3 now hold the right screenshots relative to each other, but this block is specifically about rule chains, alarm activation and Telegram/SMS/email notifications, and the image it lands on is four historical trend charts (coolant temperature, oil pressure, mains power, battery voltage) with an "Engine stop" annotation — no alarm, no rule chain, no notification anywhere in the frame.

So the strongest claim in the section is the one with no supporting visual. A rule-chain screenshot, an alarms table, or a Telegram/email notification capture would carry real weight here. If nothing like that is obtainable from the customer, dropping the image from this block would still read better than an unrelated one.

{
title: 'ThingsBoard implementation',
text: 'CUBA deployed ThingsBoard as the central monitoring layer for diesel generator assets. CUBA RePort gateways poll generator controllers over industrial protocols and publish structured telemetry to the platform. ThingsBoard stores telemetry, manages assets and dashboards, processes alarm rules, and sends notifications to responsible users.',
image: '/images/case-studies/cuba-iot-platform-5.webp',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other re-raise, now more visible than it was before the swap. Comparing the four screenshots: image 4 is the full 12-tile historical dashboard, and images 3 and 5 are both crops of that same dashboard — image 3 is its Coolant Temperature / Oil Pressure / Mains power / Battery Voltage tiles, image 5 its Mains current / Mains power / Generator current / Generator power tiles. Blocks 3, 4 and 5 therefore render three overlapping views of one dashboard, with image 4 containing both of the others.

What makes it worth acting on rather than filing as a nit is what the cropping costs. In image 5 the whole top row (Mains current, Mains power) sits flat at zero. In image 4 that same flat line is legible, because the annotation "Power network shutdown" is right there explaining it — mains died, which is the whole reason the generator ran. Cropped away from that annotation it just reads as an empty chart, so the crop loses the one thing that made the data tell a story.

And this block is the retitled architecture block — gateways polling controllers over Modbus and publishing telemetry. A dashboard crop, half of it flat, is a weak illustration for it; an architecture or topology diagram is the natural fit and would also break up three consecutive chart grids.

If the design supplies exactly these five crops then this is a question for the designer rather than a change to make here — but with block 2's screenshot re-exported already, 3/4/5 is now the weakest part of the page's visuals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants